package org.naxx.security.parser;

import java.util.ArrayList;
import java.util.List;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.naxx.security.datatype.DataTypeResolver;
import org.naxx.security.datatype.DataTypeValueResolver;
import org.naxx.security.function.FunctionType;
import org.naxx.security.function.FunctionTypeResolver;
import org.naxx.security.model.Target;
import org.naxx.security.model.impl.TargetImpl;
import org.naxx.security.model.impl.match.AttributeDesignatorImpl;
import org.naxx.security.model.impl.match.MatchContainerImpl;
import org.naxx.security.model.impl.match.MatchImpl;
import org.naxx.security.model.impl.match.ValueHolderImpl;
import org.naxx.security.model.match.MatchContainer;
import org.naxx.security.model.match.MatchParentType;
import org.naxx.security.parser.util.ParsingUtils;
import org.naxx.security.parser.util.QNames;

public final class TargetParser extends AbstractXmlParser<Target> {
	
	private DataTypeResolver dataTypeResolver;
	private DataTypeValueResolver dataTypeValueResolver;
	private FunctionTypeResolver functionTypeResolver;
	
	public void setDataTypeResolver(DataTypeResolver dataTypeResolver) {
		this.dataTypeResolver = dataTypeResolver;
	}
	public void setDataTypeValueResolver(DataTypeValueResolver dataTypeValueResolver) {
		this.dataTypeValueResolver = dataTypeValueResolver;
	}
	public void setFunctionTypeResolver(FunctionTypeResolver functionTypeResolver) {
		this.functionTypeResolver = functionTypeResolver;
	}
	
	/**
	 * Precondition: cursor points to start of Target element.<br>
	 * Postcondition: cursor points to closing Target element.
	 */
	public Target parse(XMLEventReader reader) throws Exception {
		List<MatchContainer> actions = null;
		List<MatchContainer> environments = null;
		List<MatchContainer> resources = null;
		List<MatchContainer> subjects = null;
		
		XMLEvent event = reader.nextTag(); // we now point to [Actions|Environments|Resources|Subjects], or end Target element
		
		if(event.isEndElement() && "Target".equals(event.asEndElement().getName().getLocalPart())) {
			return null;
		}
		
		boolean endOfTargetElementReached = false;
		while(!endOfTargetElementReached) {
			if("Actions".equals(event.asStartElement().getName().getLocalPart())) {
				actions = this.parseTargetChild(reader, "Action"); 
				event = reader.nextTag();
			}
			else if("Environments".equals(event.asStartElement().getName().getLocalPart())) {
				environments = this.parseTargetChild(reader, "Environment"); 
				event = reader.nextTag();
			}
			else if("Resources".equals(event.asStartElement().getName().getLocalPart())) {
				resources = this.parseTargetChild(reader, "Resource"); 
				event = reader.nextTag();
			}
			else if("Subjects".equals(event.asStartElement().getName().getLocalPart())) {
				subjects = this.parseTargetChild(reader, "Subject"); 
				event = reader.nextTag();
			}
			else {
				throw new IllegalStateException("Target parser not implemented correctly, unexpected element " +
						"'" + event.asStartElement().getName().getLocalPart() + "'");  
			}
			if(event.getEventType() == XMLStreamConstants.END_ELEMENT) {
				endOfTargetElementReached = "Target".equals(event.asEndElement().getName().getLocalPart());
			}
		}
		
		return new TargetImpl(actions, environments, resources, subjects);
	}
	
	
	/**
	 * Current position of cursor is [Actions|Environments|Resources|Subjects] 
	 * element. After execution, current cursor will point to matching closing
	 * element. For example: on entry: Actions; on exit: Actions.
	 * 
	 * @param childName one of following strings [Action|Environment|Resource|Subject]
	 */
	public List<MatchContainer> parseTargetChild(XMLEventReader reader, String childName) throws Exception {
		List<MatchContainer> result = new ArrayList<MatchContainer>();
		XMLEvent event = reader.nextTag(); //cursor now points to first 
										   //[Action|Environment|Resource|Subject] element
		ParsingUtils.assertEvent(XMLStreamConstants.START_ELEMENT, 
				event.getEventType());
		
		do { // iterate until end childName element is encountered	
			MatchContainerImpl matchContainer = new MatchContainerImpl();
			matchContainer.setMatchParentType(MatchParentType.valueOf(childName.toUpperCase()));
			
			event = reader.nextTag(); //cursor now points to start of Match element
			ParsingUtils.assertEvent(XMLStreamConstants.START_ELEMENT, 
					event.getEventType());
			
			// cursor points now to start of Match element
			while(event.getEventType() == XMLStreamConstants.START_ELEMENT && 
					(childName + "Match").equals(event.asStartElement().getName().getLocalPart())) {
				
				StartElement matchStart = event.asStartElement();
				
				event = reader.nextTag(); //cursor now points to AttributeValue element 
				ParsingUtils.assertEvent(XMLStreamConstants.START_ELEMENT, 
						event.getEventType());
				StartElement valueHolderStart = event.asStartElement();
				
				event = reader.nextEvent(); //cursor now points to characters of AttributeValue element
				ParsingUtils.assertEvent(XMLStreamConstants.CHARACTERS, 
						event.getEventType());
				Characters valueHolderCharacters = event.asCharacters();
				
				event = reader.nextEvent(); //cursor now points to end of AttributeValue element
				ParsingUtils.assertEvent(XMLStreamConstants.END_ELEMENT, 
						event.getEventType());
				
				event = reader.nextTag(); //cursor now points to AttributeDesignator element
				ParsingUtils.assertEvent(XMLStreamConstants.START_ELEMENT, 
						event.getEventType());
				StartElement attributeDesignatotStart = event.asStartElement();
				
				AttributeDesignatorImpl designator = new AttributeDesignatorImpl();
				designator.setAttributeId(attributeDesignatotStart.getAttributeByName(QNames.ATTRIBUTE_ID_ATTRIBUTE).getValue());
				designator.setDataType(this.dataTypeResolver.resolveDataType(
						attributeDesignatotStart.getAttributeByName(QNames.DATA_TYPE_ATTRIBUTE).getValue()));
				Attribute mustBePresent = attributeDesignatotStart.getAttributeByName(QNames.MUST_BE_PRESENT_ATTRIBUTE);
				if(mustBePresent != null) {
					designator.setMustBePresent(Boolean.valueOf(mustBePresent.getValue()));
				}
				Attribute issuer = attributeDesignatotStart.getAttributeByName(QNames.ISSUER_ATTRIBUTE);
				if(issuer != null) {
					designator.setIssuer(issuer.getValue());
				}
				
				ValueHolderImpl valueHolder = new ValueHolderImpl();
				valueHolder.setDataType(this.dataTypeResolver.resolveDataType(valueHolderStart.getAttributeByName(
						QNames.DATA_TYPE_ATTRIBUTE).getValue()));
				valueHolder.setValue(this.dataTypeValueResolver.resolveValue(valueHolder.getDataType(), 
						valueHolderCharacters.getData()));
				
				FunctionType functionType = this.functionTypeResolver.resolve(
						matchStart.getAttributeByName(QNames.MATCH_ID_ATTRIBUTE).getValue());
				MatchImpl match = new MatchImpl(designator, valueHolder, functionType);
				matchContainer.addMatch(match);
				
				event = reader.nextTag(); //cursor now points to end of AttributeDesignator element
				ParsingUtils.assertEvent(XMLStreamConstants.END_ELEMENT, 
						event.getEventType());
				
				event = reader.nextTag(); //cursor now points to end of Match element
				ParsingUtils.assertEvent(XMLStreamConstants.END_ELEMENT, 
						event.getEventType());
				
				event = reader.nextTag();
			}
			result.add(matchContainer);
			event = reader.nextTag();
		} while ((event.getEventType() != XMLStreamConstants.END_ELEMENT));
		
		return result;
	}
}
